/*
 * Copyright 2008 Lucas Tan.
 * This copyright notice must always remain intact whenever you use or distribute 
 * the source code contained in this file.
 * Any usage of any code in this file, either in derived work or
 * through the use of external linkage,
 * must always be credited with the author's name.
 * 
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 * 
 * */
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Reflection;
using System.Diagnostics;

namespace PassKeeper.Data
{
	/// <summary>
	/// Represents a collection of account.
	/// </summary>
	[DebuggerDisplay("Count = {Count}")]
	public sealed class AccountCollection : 
		Element,
		IEnumerable<Account>
	{
		#region Static members

		internal const string ElementName = "Accounts";

		/// <summary>
		/// Gets the <see cref="IComparer`1"/> interface that decides
		/// how names are sorted.
		/// </summary>
		public static IComparer<string> NameComparer
		{
			get
			{
				return StringComparer.Ordinal;
			}
		}

		/// <summary>
		/// Gets the <see cref="IEqualityComparer`1"/> interface that
		/// can calculate hash code of names and can decide whether
		/// two names are the same.
		/// </summary>
		public static IEqualityComparer<string> NameEqualityComparer
		{
			get
			{
				return StringComparer.Ordinal;
			}
		}

		#endregion

		#region Non-static members

		private SortedList<string, Account> _dict;

		internal AccountCollection(Database owner)
			: base(owner)
		{
			_dict = new SortedList<string, Account>(NameComparer);
		}


		/// <summary>
		/// Clone this object and specify an owner database
		/// for the clone.
		/// </summary>
		/// <param name="ownerDatabase">Owner database of the clone.</param>
		/// <returns>A clone of this object. Will never return null.</returns>
		protected internal override Element Clone(Database ownerDatabase)
		{
			if (ownerDatabase == null)
			{
				throw new ArgumentNullException("ownerDatabase");
			}

			AccountCollection clonedAccounts = new AccountCollection(ownerDatabase);

			foreach (Account account in _dict.Values)
			{
				clonedAccounts.Add((Account) account.Clone(ownerDatabase));
			}

			return clonedAccounts;
		}

		/// <summary>
		/// Checks whether this collection contains an account that has
		/// a specified name.
		/// </summary>
		/// <param name="name">Name of the account to check for.</param>
		/// <returns>true if the name exists; false otherwise.</returns>
		public bool Contains(string name)
		{
			return _dict.ContainsKey(name);
		}

		/// <summary>
		/// Removes an account that has a specified name.
		/// </summary>
		/// <param name="name">Name of the account to remove.</param>
		/// <returns>true if successfully removed; false if the specified account
		/// does not exist.</returns>
		public bool Remove(string name)
		{
			Account account;

			if (_dict.TryGetValue(name, out account))
			{
				if (_dict.Remove(name))
				{
					account.Parent = null;
					return true;
				}
			}

			return false;
		}

		/// <summary>
		/// Removes all accounts from this collection.
		/// </summary>
		public void Clear()
		{
			foreach (Account account in _dict.Values)
			{
				account.Parent = null;
			}

			_dict.Clear();
		}

		/// <summary>
		/// Adds an account to this collection.
		/// </summary>
		/// <param name="account">Account to be added.</param>
		/// <exception cref="ArgumentNullException"><paramref name="account"/> is null.</exception>
		/// <exception cref="ArgumentException">Owner database of <paramref name="account"/>
		/// is different from that of this collection. -or- <paramref name="account"/> already has
		/// a parent. -or- There is already another account with the same name. -or-
		/// The account to be added is a parent/ancestor of this collection.</exception>
		public void Add(Account account)
		{
			if (account == null)
			{
				throw new ArgumentNullException("account");
			}

			if (!object.ReferenceEquals(account.OwnerDatabase, this.OwnerDatabase))
			{
				throw new ArgumentException("Owner database mismatch.", "account");
			}

			if (account.Parent != null)
			{
				throw new ArgumentException("Already has a parent.", "account");
			}

			_dict.Add(account.Name, account);

			bool ok = false;

			try
			{
				account.Parent = this;
				ok = true;
			}
			finally
			{
				if (!ok)
				{
					/*We reach here when the account to be added is 
					 * an ancestor/parent of this collection.*/
					_dict.Remove(account.Name);
				}
			}
		}

		/// <summary>
		/// Load this object with data supplied by an <see cref="XmlElement"/>.
		/// </summary>
		/// <param name="elem">Element to supply the data. This element will not be modified.</param>
		/// <remarks>The state of this object is unmodified if deserialization fails.</remarks>
		/// <exception cref="ArgumentNullException"><paramref name="elem"/> is null.</exception>
		/// <exception cref="ParseException">There is an error in parsing data.</exception>
		public override void Deserialize(XmlElement elem)
		{
			if (elem == null)
			{
				throw new ArgumentNullException("elem");
			}

			SortedList<string, Account> newDict = 
				new SortedList<string, Account>(_dict.Comparer);

			bool ok = false;

			try
			{
				XmlNodeList nodeList = ParserUtil.GetChildren(elem, Account.ElementName);

				foreach (XmlNode node in nodeList)
				{
					Account account = new Account(this.OwnerDatabase, (XmlElement) node);

					if (newDict.ContainsKey(account.Name))
					{
						throw new ElementParseException(
							node.Name, "Duplicate name."
						);
					}

					newDict.Add(account.Name, account);
					account.Parent = this;
				}
				ok = true;
			}
			finally
			{
				if (!ok)
				{
					foreach (Account account in newDict.Values)
					{
						account.Parent = null;
					}
				}
			}

			this.Clear();
			_dict = newDict;
		}

		/// <summary>
		/// Save the data in this object into an <see cref="XmlElement"/> using the version specified
		/// by the <see cref="P:Database.Version">Version</see> property of this object's owner database.
		/// </summary>
		/// <param name="elem">Element which will hold the data.</param>
		/// <remarks>The state of <paramref name="elem"/> will be unknown if any exception occurs
		/// during the process. However, the state of this object will always be unmodified.</remarks>
		/// <exception cref="ArgumentNullException"><paramref name="elem"/> is null.</exception>
		public override void Serialize( XmlElement elem)
		{
			if (elem == null)
			{
				throw new ArgumentNullException("elem");
			}

			foreach (Account account in _dict.Values)
			{
				XmlElement accountElem = elem.OwnerDocument.CreateElement(account.TagName);
				account.Serialize(accountElem);
				elem.AppendChild(accountElem);
			}
		}

		/// <summary>
		/// Ensures the protected values of this object are fully decrypted and
		/// optionally release the cached encrypted version of the protected values.
		/// </summary>
		internal void EnsureDecrypted(bool releaseCache)
		{
			foreach (Account account in _dict.Values)
			{
				account.EnsureDecrypted(releaseCache);
			}
		}

		/// <summary>
		/// Gets the XML tag name of this collection.
		/// </summary>
		/// <value>Will never return null.</value>
		public override string TagName
		{
			get
			{
				return ElementName;
			}
		}

		/// <summary>
		/// Gets the number of accounts in this collection.
		/// </summary>
		public int Count
		{
			get
			{
				return _dict.Count;
			}
		}

		/// <summary>
		/// Gets the account that has a specified name.
		/// </summary>
		/// <param name="name">Name of the account to retrieve.</param>
		/// <returns></returns>
		public Account this[string name]
		{
			get
			{
				return _dict[name];
			}
		}

		/// <summary>
		/// Gets an array of the names of the accounts in this collection.
		/// </summary>
		/// <value>Will return an empty array if there is no account
		/// in this collection.</value>
		public string[] Names
		{
			get
			{
				return (new List<string>(_dict.Keys)).ToArray();
			}
		}

		public IEnumerator GetEnumerator()
		{
			return _dict.Values.GetEnumerator();
		}

		#region IEnumerable<Account> Members

		IEnumerator<Account> IEnumerable<Account>.GetEnumerator()
		{
			return _dict.Values.GetEnumerator();
		}

		#endregion

		/*Non-static Members*/
		#endregion 
	}
}
